using UnityEngine;
using System.Collections.Generic;
using Newtonsoft.Json;
using System.IO;
using ICSharpCode.SharpZipLib.Zip;
using Frankfort.Threading.Internal;
using System.Threading;
using ICSharpCode.SharpZipLib.GZip;
using System.Runtime.InteropServices;

public static class CompressType
{
	public const uint NONE = 0;
	public const uint LZMA = 1;
	public const uint GZIP = 2;
	public const uint ZIP = 3;
	public const uint BROTLI = 4;
}

public enum DecompressStep
{
	none,
	ready,
	decompressing,
	decompressed
}

class Info
{
	public string srcPath;
	public string destPath;
	public HashSet<CompressManager.DecompressCallback> callBacks;
	public bool ret;
	public Thread thread;
	public DecompressStep step = DecompressStep.none;
	public System.DateTime startDecompressTime;
	public float spanTime;
}

public class CompressManager: Singleton<CompressManager>
{
	private static string cachePath = CommonUtil.cachePath + "/compress_cache";
	
    static CompressManager()
    {
        CommonUtil.CreateDir(cachePath);
    }

	public delegate void DecompressCallback(string inFile, string outFile, bool sucess);
	private Dictionary<string, Info> threadInfoMap = new Dictionary<string, Info>();
	
//#if UNITY_EDITOR
	private static int BrotliDecompress(string inputPath, string outputPath){return 0;}
//#else
//#if UNITY_ANDROID
//	[DllImport("brotli")]
//#else
//	[DllImport("__Internal")]
//#endif
//	private static extern int BrotliDecompress(string inputPath, string outputPath);
//#endif

	delegate bool StreamFeof();
	delegate int StreamRead();
	delegate bool StreamError();

//	#if UNITY_EDITOR
	private static int BrotliDecompressStream2(StreamFeof streamFeof, StreamRead streamRead, StreamError streamError, System.IntPtr input, int bufLen, string outputPath){return 0;}
//	#else
//	#if UNITY_ANDROID
//	[DllImport("brotli")]
//	#else
//	[DllImport("__Internal")]
//	#endif
//	private static extern int BrotliDecompressStream2(StreamFeof streamFeof, StreamRead streamRead, StreamError streamError, System.IntPtr input, int bufLen, string outputPath);
//	#endif

	public static void createDir()
	{
		CommonUtil.CreateDir(cachePath);
	}
	
	public void delTempFiles()
	{
		if(!Directory.Exists(cachePath))
		{
			return;
		}
		
		string[] paths = Directory.GetFiles(cachePath, "*", SearchOption.AllDirectories);
		for(int i = 0; i < paths.Length; i++)
		{
			CommonUtil.Delete(paths[i]);
		}
	}
	
	public void Cancel(string inFile, string outFile, CompressManager.DecompressCallback callBack)
	{
		if(inFile == null || outFile == null)
		{
			return;
		}

		string key = inFile + "->" + outFile;
		
		Info info = null;
		if(!threadInfoMap.TryGetValue(key, out info))
		{
			return;
		}
		
		Cancel(info, callBack);
	}
	
	private void Cancel(object obj, CompressManager.DecompressCallback callBack)
	{
		if(obj == null)
		{
			return;
		}
		
		Info info = (Info)obj;
		if(info.callBacks != null)
		{
			info.callBacks.Remove(callBack);
		}
        //info.thread.Abort();  //由于iL2Cpp不支持Abort方法，屏蔽终止方法，让线程自行终止。
	}
	
	public DecompressStep getDecompressStep(object obj)
	{
		if(obj == null)
		{
			return DecompressStep.none;
		}
		
		Info info = (Info)obj;
		return info.step;
	}
	
	public float getSpanTime(object obj)
	{
		if(obj == null)
		{
			return 0;
		}
		
		Info info = (Info)obj;
		return info.spanTime;
	}
	
	public object Decompress(uint compress, string inFile, string outFile, DecompressCallback callback)
	{
		string key = inFile + "->" + outFile;
		Info info = null;
		if(!threadInfoMap.TryGetValue(key, out info))
		{
			info = new Info();
			info.srcPath = inFile;
			info.destPath = outFile;
			
			info.step = DecompressStep.ready;
			info.startDecompressTime = System.DateTime.Now;
			Thread _workThread = SingleThreadStarter.StartSingleThread(() =>
			                                                           {
				info.step = DecompressStep.decompressing;
				try
				{
					info.ret = DecompressFile(compress, info.srcPath, info.destPath);
					if(info.ret)
					{
						CommonUtil.Delete(info.srcPath);
					}
				}
				catch(System.Exception e)
				{
					info.ret = false;
					Debug.LogException(e);
				}
				MainThreadDispatcher.DispatchToMainThread(DecompressCallBack, info);
			});
			
			info.thread = _workThread;
			threadInfoMap.Add(key, info);
		}

		if(info.callBacks == null)
		{
			info.callBacks = new HashSet<DecompressCallback>();
		}
		info.callBacks.Add(callback);

		return info;
	}
	
	private void DecompressCallBack(object response)
	{
		Info info = (Info)response;
		info.step = DecompressStep.decompressed;
		info.spanTime = (float)System.DateTime.Now.Subtract(info.startDecompressTime).TotalSeconds;
		string key = info.srcPath + "->" + info.destPath;
		if(threadInfoMap.Remove(key))
		{
			HashSet<CompressManager.DecompressCallback> temp = info.callBacks;
			info.callBacks = null;

			if(temp != null)
			{
				HashSet<CompressManager.DecompressCallback>.Enumerator enumerator = temp.GetEnumerator();
				while(enumerator.MoveNext())
				{
					enumerator.Current(info.srcPath, info.destPath, info.ret);
				}
			}
		}
	}
	
	public static bool CompressFile(uint compress, string inFile, string outFile)
	{
//		if(compress == CompressType.LZMA)
//		{
//			return CompressFileLZMA(inFile, outFile);
//		}
		//		else if(compress == Compress.ZIP)
		//		{
		//
		//		}
//		else 
            if(compress == CompressType.GZIP)
		{
			return CompressFileGZIP(inFile, outFile);
		}
		return false;
	}

    public static bool DecompressBytes(uint compress, byte[] inData, int originalSize, out byte[] outData)
    {
        outData = null;
        switch (compress)
        {
            case CompressType.NONE:
                {
                    outData = inData;
                    return true;
                }
            case CompressType.LZMA:
                {
                    return DecompressBytesLZMA(inData, originalSize, out outData);
                }
        }
        
        return false;
    }

    public static bool CompressBytes(uint compress, byte[] inData, out byte[] outData)
    {
        outData = null;
        switch (compress)
        {
            case CompressType.NONE:
            {
                outData = inData;
                return true;
            }
            case CompressType.LZMA:
            {
                    return CompressBytesLZMA(inData, out outData);
            }
        }

        //      else if(compress == Compress.ZIP)
        //      {
        //
        //      }
        //      else 
//        if(compress == CompressType.GZIP)
//        {
//            return CompressFileGZIP(inFile, outFile);
//        }

        return false;
    }
	
	private static bool CompressFileGZIP(string inFile, string outFile)
	{
		if(inFile == outFile)
		{
			return false;
		}
		FileStream input = new FileStream(inFile, FileMode.Open);
		
		FileStream output = new FileStream(outFile, FileMode.Create);
		GZipOutputStream outStream = new GZipOutputStream(output);
		
		byte[] buf = new byte[1024];
		while(true)
		{
			int readLen = input.Read(buf, 0, buf.Length);
			if(readLen <= 0)
			{
				break;
			}
			outStream.Write(buf, 0, readLen);
		}
		
		outStream.Flush();
		outStream.Finish();
		
		output.Flush();
		output.Close();
		output = null;
		input.Close();
		input = null;
		
		return true;
	}
	
	private static bool CompressFileLZMA(string inFile, string outFile)
	{
		if(inFile == outFile)
		{
			return false;
		}
		SevenZip.Compression.LZMA.Encoder coder = new SevenZip.Compression.LZMA.Encoder();
		FileStream input = new FileStream(inFile, FileMode.Open);
		FileStream output = new FileStream(outFile, FileMode.Create);
		
		// Write the encoder propertiess
		coder.WriteCoderProperties(output);
		
		// Write the decompressed file size.
		output.Write(System.BitConverter.GetBytes(input.Length), 0, 8);
		
		// Encode the file.
		coder.Code(input, output, input.Length, -1, null);
		output.Flush();
		output.Close();
		output = null;
		input.Close();
		input = null;
		
		return true;
	}
	
    private static bool CompressBytesLZMA(byte[] inData, out byte[] outData)
    {
        if(inData == null)
        {
            outData = null;
            return false;
        }

        SevenZip.Compression.LZMA.Encoder coder = new SevenZip.Compression.LZMA.Encoder();
        MemoryStream input = new MemoryStream(inData);
        MemoryStream output = new MemoryStream();

        // Write the encoder propertiess
        coder.WriteCoderProperties(output);

        // Write the decompressed file size.
        output.Write(System.BitConverter.GetBytes(input.Length), 0, 8);

        // Encode the file.
        coder.Code(input, output, input.Length, -1, null);

        input.Close();
        input = null;

        outData = output.ToArray();

        output.Flush();
        output.Close();
        output = null;

        return true;
    }

	public static bool DecompressFile(uint compress, string inFile, string outFile)
	{
		if(compress == CompressType.NONE)
		{
			return CommonUtil.Move(inFile, outFile);
		}
		else if(compress == CompressType.LZMA)
		{
			return DecompressFileLZMA(inFile, outFile);
		}
		else if(compress == CompressType.GZIP)
		{
			return DecompressFileGZIP(inFile, outFile);
		}
		else if(compress == CompressType.ZIP)
		{
			return DecompressFileZIP(inFile, outFile);
		}
		else if(compress == CompressType.BROTLI)
		{
//			return decompress(Marshal.StringToHGlobalAnsi(inFile), Marshal.StringToHGlobalAnsi(outFile)) == 0;
//			return DecompressFileBROTLI(inFile, outFile);
		}
		return false;
	}

//	private static bool DecompressFileBROTLI(string inFile, string outFile)
//	{
//		if(inFile == outFile)
//		{
//			return false;
//		}
//
//		bool ret = false;
//		
//		string cacheFilePath = CommonUtil.md5(outFile);
//		cacheFilePath = cachePath + "/" + cacheFilePath;
//
//		try
//		{
//			CommonUtil.Delete(cacheFilePath);
//			ret = BrotliDecompress(inFile, cacheFilePath) == 0;
//			if(ret)
//			{
//				ret = CommonUtil.Move(cacheFilePath, outFile);
//			}
//		}
//		catch(System.SystemException e)
//		{
//			Debug.LogException(e);
//		}
//
//		if(!ret)
//		{
//			CommonUtil.Delete(cacheFilePath);
//		}
//		return ret;
//	}

    private static bool DecompressBytesLZMA(byte[] inData, int originalSize, out byte[] outData)
    {
        outData = null;
        if(inData == null)
        {
            return false;
        }

        bool ret = false;
        MemoryStream input = null;
        MemoryStream output = null;
        try
        {
            input = new MemoryStream(inData);
            if(originalSize > 0)
            {
                outData = new byte[originalSize];
            }
            output = new MemoryStream(outData);
            ret = DecompressLZMA(input, output);
            if(ret)
            {
                outData = output.ToArray();
            }
        }
        catch(System.SystemException e)
        {
            Debug.LogException(e);
        }
        finally
        {
            if(input != null)
            {
                input.Close();
                input = null;
            }

            if(output != null)
            {
                output.Close();
                output = null;
            }
        }
        return ret;
    }

	private static bool DecompressFileLZMA(string inFile, string outFile)
	{
		if(inFile == outFile)
		{
			return false;
		}
		
		bool ret = false;
		FileStream input = null;
		try
		{
			input = File.OpenRead(inFile);
			ret = DecompressFileLZMA(input, outFile);
		}
		catch(System.SystemException e)
		{
			Debug.LogException(e);
		}
		finally
		{
			if(input != null)
			{
				input.Close();
				input = null;
			}
		}
		return ret;
	}
	
    public static bool DecompressStreamToFile(uint compress, Stream input, string outFile, System.Action<uint> progressCallback = null)
	{
		if(compress == CompressType.NONE)
		{
            return Copy(input, outFile, progressCallback);
		}
//		else if(compress == CompressType.LZMA)
//		{
//			return DecompressFileLZMA(input, outFile);
//		}
		//		else if(compress == Compress.ZIP)
		//		{
		//
		//		}
		else if(compress == CompressType.GZIP)
		{
			return DecompressFileGZIP(input, outFile);
		}
		else if(compress == CompressType.BROTLI)
		{
//			return DecompressFileBROTLI(input, outFile);
		}
		return false;
	}


	class StreamDecompressWrapper
	{
		private Stream _stream;
		public byte[] _data;
		public GCHandle unmanaged_data_handle;
		private bool isEnd = false;
		private bool isError = false;

		public StreamDecompressWrapper(Stream input, int bufLen)
		{
			this._stream = input;
			_data = new byte[bufLen];
			unmanaged_data_handle = GCHandle.Alloc(_data, GCHandleType.Pinned);
		}

		public void release()
		{
			unmanaged_data_handle.Free();
		}

		public bool eof()
		{
			return isEnd;
		}

		public int read()
		{
			int readedLen = 0;
			try
			{
				readedLen = _stream.Read(_data, 0, _data.Length);
				if(readedLen < _data.Length || readedLen == 0)
				{
					isEnd = true;
				}
			}
			catch(System.Exception e)
			{
				isError = true;
				Debug.LogException(e);
			}
			return readedLen;
		}

		public bool error()
		{
			return isError;
		}
	}
//	public static bool DecompressFileBROTLI(Stream input, string outFile)
//	{
//		if(input == null)
//		{
//			return false;
//		}
//		
//		bool ret = false;
//		
//		string cacheFilePath = CommonUtil.md5(outFile);
//		cacheFilePath = cachePath + "/" + cacheFilePath;
//
//		StreamDecompressWrapper wapper = null;
//		try
//		{
//			CommonUtil.Delete(cacheFilePath);
//
//			wapper = new StreamDecompressWrapper(input, 65536);
//			ret = BrotliDecompressStream2(wapper.eof, wapper.read, wapper.error, wapper.unmanaged_data_handle.AddrOfPinnedObject(), wapper._data.Length, cacheFilePath) == 0;
//			if(ret)
//			{
//				ret = CommonUtil.Move(cacheFilePath, outFile);
//			}
//
////			Debug.LogError("test_decompress:" + outFile + " " + ret);
//		}
//		catch(System.SystemException e)
//		{
////			Debug.LogError("test_decompress1:" + outFile + " " + ret);
//			Debug.LogException(e);
//		}
//		finally
//		{
//			if(wapper != null)
//			{
////				Debug.LogError("test_decompress2:" + outFile);
//				wapper.release();
//			}
//		}
//		
//		if(!ret)
//		{
//			CommonUtil.Delete(cacheFilePath);
//		}
//		return ret;
//	}
	
	public static bool DecompressFileGZIP(Stream input, string outFile)
	{
		bool ret = false;
		
		string cacheFilePath = CommonUtil.md5(outFile);
		cacheFilePath = cachePath + "/" + cacheFilePath;
		
		FileStream output = null;
		try
		{
			GZipInputStream inputStream = new GZipInputStream(input);
			
			output = new FileStream(cacheFilePath, FileMode.Create);
			byte[] buffer = new byte[1024];
			while(true)
			{
				int readLen = inputStream.Read(buffer, 0, buffer.Length);
				if(readLen <= 0)
				{
					break;
				}
				output.Write(buffer, 0, readLen);
			}
			
			output.Flush();
			output.Close();
			output = null;
			
			ret = CommonUtil.Move(cacheFilePath, outFile);
		}
		catch(System.SystemException e)
		{
			ret = false;
			Debug.LogException(e);
		}
		finally
		{
			if(output != null)
			{
				output.Close();
				output = null;
			}
		}

		if(!ret)
		{
			CommonUtil.Delete(cacheFilePath);
		}

		return ret;
	}

    public static bool Copy(Stream input, string outFile, System.Action<uint> progressCallback = null)
	{
		bool ret = false;
		
		string cacheFilePath = CommonUtil.md5(outFile);
		cacheFilePath = cachePath + "/" + cacheFilePath;
		
		FileStream output = null;
		try
		{
			output = new FileStream(cacheFilePath, FileMode.Create);

			int bufLen = 1024;
			byte[] buffer = new byte[bufLen];
			uint sum = 0;

			while(true)
			{
				int readLen = input.Read(buffer, 0, bufLen);
				
				if(readLen > 0)
				{
					output.Write(buffer, 0, readLen);
                    sum += (uint)readLen;
                    if(progressCallback != null)
                    {
                        progressCallback(sum);
                    }
				}
				
				if(readLen < bufLen || readLen == 0)
				{
					break;
				}
			}

			output.Flush();
			output.Close();
			output = null;

			ret = CommonUtil.Move(cacheFilePath, outFile);
		}
		catch(System.SystemException e)
		{
			Debug.LogException(e);
		}
		finally
		{
			if(output != null)
			{
				output.Close();
				output = null;
			}
		}

		if(!ret)
		{
			CommonUtil.Delete(cacheFilePath);
		}
		return ret;
	}
	
	public static bool DecompressFileLZMA(Stream input, string outFile)
	{
		bool ret = false;
		
		string cacheFilePath = CommonUtil.md5(outFile);
		cacheFilePath = cachePath + "/" + cacheFilePath;
		
		FileStream output = null;
		try
		{
			output = new FileStream(cacheFilePath, FileMode.Create);
			
			SevenZip.Compression.LZMA.Decoder coder = new SevenZip.Compression.LZMA.Decoder();
			
			// Read the decoder properties
			byte[] properties = new byte[5];
			input.Read(properties, 0, 5);
			
			// Read in the decompress file size.
			byte [] fileLengthBytes = new byte[8];
			input.Read(fileLengthBytes, 0, 8);
			long fileLength = System.BitConverter.ToInt64(fileLengthBytes, 0);
			
			// Decompress the file.
			coder.SetDecoderProperties(properties);
			coder.Code(input, output, input.Length, fileLength, null);
			output.Flush();
			output.Close();
			output = null;
			
			ret = CommonUtil.Move(cacheFilePath, outFile);
		}
		catch(System.SystemException e)
		{
			Debug.LogException(e);
		}
		finally
		{
			if(output != null)
			{
				output.Close();
				output = null;
			}
		}

		if(!ret)
		{
			CommonUtil.Delete(cacheFilePath);
		}
		return ret;
	}

    public static bool DecompressLZMA(Stream input, Stream output)
    {
        bool ret = false;

//        string cacheFilePath = CommonUtil.md5(outFile);
//        cacheFilePath = cachePath + "/" + cacheFilePath;

        try
        {
            SevenZip.Compression.LZMA.Decoder coder = new SevenZip.Compression.LZMA.Decoder();

            // Read the decoder properties
            byte[] properties = new byte[5];
            input.Read(properties, 0, 5);

            // Read in the decompress file size.
            byte [] fileLengthBytes = new byte[8];
            input.Read(fileLengthBytes, 0, 8);
            long fileLength = System.BitConverter.ToInt64(fileLengthBytes, 0);

            // Decompress the file.
            coder.SetDecoderProperties(properties);
            coder.Code(input, output, input.Length, fileLength, null);
            output.Flush();
            output.Close();
            output = null;
            ret = true;
        }
        catch(System.SystemException e)
        {
            Debug.LogException(e);
        }
        
//        if(!ret)
//        {
//            CommonUtil.Delete(cacheFilePath);
//        }
        return ret;
    }
	
	private static bool DecompressFileGZIP(string inFile, string outFile)
	{
		if(inFile == outFile)
		{
			return false;
		}
		
		bool ret = false;
		FileStream input = null;
		try
		{
			input = File.OpenRead(inFile);
			ret = DecompressFileGZIP(input, outFile);
		}
		catch(System.SystemException e)
		{
			Debug.LogException(e);
		}
		finally
		{
			if(input != null)
			{
				input.Close();
				input = null;
			}
		}
		return ret;
	}
	
	private static bool DecompressZipEntry(ZipEntry zipEntry, Stream inputStream, string outputPath)
	{
		if(zipEntry == null || outputPath == null)
		{
			return false;
		}
		
		string realOutputPath = outputPath + "/" + zipEntry.Name;
		if(zipEntry.IsDirectory)
		{
			Directory.CreateDirectory(realOutputPath);
		}
		else if(zipEntry.IsFile)
		{
			string dir = Path.GetDirectoryName(realOutputPath);
			Directory.CreateDirectory(dir);
			FileStream outStream = null;
			try
			{
				outStream = File.Open(realOutputPath, FileMode.Create);
				int bufLen = 1024;
				byte[] buffer = new byte[bufLen];
				while(true)
				{
					int readLen = inputStream.Read(buffer, 0, bufLen);
					if(readLen <= 0)
					{
						break;
					}
					outStream.Write(buffer, 0, readLen);
				}
			}
			catch(System.Exception e)
			{
				Debug.LogException(e);
				return false;
			}
			finally
			{
				if(outStream != null)
				{
					outStream.Close();
					outStream = null;
				}
			}
		}
		
		return true;
	}

    private static bool DecompressZipEntryToData(ZipEntry zipEntry, Stream inputStream, out byte[] outData)
    {
        outData = null;
        if(zipEntry == null)
        {
            return false;
        }


        if (zipEntry.IsFile)
        {
            MemoryStream outStream = new MemoryStream();
            try
            {
                int bufLen = 1024;
                byte[] buffer = new byte[bufLen];
                while (true)
                {
                    int readLen = inputStream.Read(buffer, 0, bufLen);
                    if (readLen <= 0)
                    {
                        break;
                    }
                    outStream.Write(buffer, 0, readLen);
                }

                outData = outStream.ToArray();
            }
            catch (System.Exception e)
            {
                Debug.LogException(e);
                return false;
            }
            finally
            {
                if (outStream != null)
                {
                    outStream.Close();
                    outStream = null;
                }
            }
        }
        else
        {
            return false;
        }

        return true;
    }
	
	private static bool DecompressFileZIP(string filePath, string outputPath)
	{
		bool ret = true;
		CommonUtil.Delete(outputPath);
		
		FileStream input = File.OpenRead(filePath);
		
		ZipInputStream zipInput = new ZipInputStream(input);
		while(true)
		{
			ZipEntry entry = zipInput.GetNextEntry();
			if(entry == null)
			{
				break;
			}
			
			if(!DecompressZipEntry(entry, zipInput, outputPath))
			{
				ret = false;
				break;
			}
		}
		
		zipInput.Close();
		zipInput = null;
		return ret;
	}

    public static bool DecompressZipEntryToData(string filePath, string entryName, out byte[] outData)
    {
        outData = null;
        bool ret = false;
        FileStream input = File.OpenRead(filePath);

        ZipInputStream zipInput = new ZipInputStream(input);
        while(true)
        {
            ZipEntry entry = zipInput.GetNextEntry();
            if(entry == null)
            {
                break;
            }

            if (entry.Name == entryName)
            {
                ret = DecompressZipEntryToData(entry, zipInput, out outData);
                break;
            }
        }

        zipInput.Close();
        zipInput = null;
        return ret;
    }
	
	//	public object decompressZipFileAsync(string filePath, string outputPath, DecompressCallback callBack)
	//	{
	//		Info info = new Info();
	//		info.srcPath = filePath;
	//		info.destPath = outputPath;
	//		info.callBack = callBack;
	//		info.step = DecompressStep.ready;
	//		info.startDecompressTime = System.DateTime.Now;
	//		Thread _workThread = SingleThreadStarter.StartSingleThread(() =>
	//		{
	//			info.step = DecompressStep.decompressing;
	//			info.ret = decompressZipFile(filePath, outputPath);
	//			MainThreadDispatcher.DispatchToMainThread(decompressCallBack, info);
	//		});
	//
	//		info.thread = _workThread;
	//		threadInfoMap.Add(filePath, info);
	//		return info;
	//	}
}